const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
const expect = std.testing.expect;
const expectError = std.testing.expectError;
const expectEqual = std.testing.expectEqual;
const minInt = std.math.minInt;
const maxInt = std.math.maxInt;

test "switch with numbers" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testSwitchWithNumbers(13);
}

fn testSwitchWithNumbers(x: u32) !void {
    const result = switch (x) {
        1, 2, 3, 4...8 => false,
        13 => true,
        else => false,
    };
    try expect(result);
}

test "switch with all ranges" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    try expect(testSwitchWithAllRanges(50, 3) == 1);
    try expect(testSwitchWithAllRanges(101, 0) == 2);
    try expect(testSwitchWithAllRanges(300, 5) == 3);
    try expect(testSwitchWithAllRanges(301, 6) == 6);
}

fn testSwitchWithAllRanges(x: u32, y: u32) u32 {
    return switch (x) {
        0...100 => 1,
        101...200 => 2,
        201...300 => 3,
        else => y,
    };
}

test "switch arbitrary int size" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO

    if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // TODO

    try expect(testSwitchArbInt(u64, 0) == 0);
    try expect(testSwitchArbInt(u64, 12) == 1);
    try expect(testSwitchArbInt(u64, maxInt(u64)) == 2);
    try expect(testSwitchArbInt(u64, 5555) == 3);

    try expect(testSwitchArbInt(i64, minInt(i64)) == 0);
    try expect(testSwitchArbInt(i64, 12) == 1);
    try expect(testSwitchArbInt(i64, maxInt(i64)) == 2);
    try expect(testSwitchArbInt(i64, -1000) == 3);

    try expect(testSwitchArbInt(u128, 0) == 0);
    try expect(testSwitchArbInt(u128, 12) == 1);
    try expect(testSwitchArbInt(u128, maxInt(u128)) == 2);
    try expect(testSwitchArbInt(u128, 5555) == 3);

    try expect(testSwitchArbInt(i128, minInt(i128)) == 0);
    try expect(testSwitchArbInt(i128, 12) == 1);
    try expect(testSwitchArbInt(i128, maxInt(i128)) == 2);
    try expect(testSwitchArbInt(i128, -1000) == 3);
}

fn testSwitchArbInt(comptime T: type, x: T) u32 {
    return switch (x) {
        minInt(T) => 0,
        10...15 => 1,
        maxInt(T) => 2,
        else => 3,
    };
}

test "implicit comptime switch" {
    const x = 3 + 4;
    const result = switch (x) {
        3 => 10,
        4 => 11,
        5, 6 => 12,
        7, 8 => 13,
        else => 14,
    };

    comptime {
        try expect(result + 1 == 14);
    }
}

test "switch on enum" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const fruit = Fruit.Orange;
    try expect(nonConstSwitchOnEnum(fruit));
}
const Fruit = enum {
    Apple,
    Orange,
    Banana,
};
fn nonConstSwitchOnEnum(fruit: Fruit) bool {
    return switch (fruit) {
        Fruit.Apple => false,
        Fruit.Orange => true,
        Fruit.Banana => false,
    };
}

test "switch statement" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try nonConstSwitch(SwitchStatementFoo.C);
}
fn nonConstSwitch(foo: SwitchStatementFoo) !void {
    const val = switch (foo) {
        SwitchStatementFoo.A => @as(i32, 1),
        SwitchStatementFoo.B => 2,
        SwitchStatementFoo.C => 3,
        SwitchStatementFoo.D => 4,
    };
    try expect(val == 3);
}
const SwitchStatementFoo = enum { A, B, C, D };

test "switch with multiple expressions" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const x = switch (returnsFive()) {
        1, 2, 3 => 1,
        4, 5, 6 => 2,
        else => @as(i32, 3),
    };
    try expect(x == 2);
}
fn returnsFive() i32 {
    return 5;
}

test "switch on type" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try expect(trueIfBoolFalseOtherwise(bool));
    try expect(!trueIfBoolFalseOtherwise(i32));
}

fn trueIfBoolFalseOtherwise(comptime T: type) bool {
    return switch (T) {
        bool => true,
        else => false,
    };
}

test "switching on booleans" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try testSwitchOnBools();
    try comptime testSwitchOnBools();
}

fn testSwitchOnBools() !void {
    try expect(testSwitchOnBoolsTrueAndFalse(true) == false);
    try expect(testSwitchOnBoolsTrueAndFalse(false) == true);

    try expect(testSwitchOnBoolsTrueWithElse(true) == false);
    try expect(testSwitchOnBoolsTrueWithElse(false) == true);

    try expect(testSwitchOnBoolsFalseWithElse(true) == false);
    try expect(testSwitchOnBoolsFalseWithElse(false) == true);
}

fn testSwitchOnBoolsTrueAndFalse(x: bool) bool {
    return switch (x) {
        true => false,
        false => true,
    };
}

fn testSwitchOnBoolsTrueWithElse(x: bool) bool {
    return switch (x) {
        true => false,
        else => true,
    };
}

fn testSwitchOnBoolsFalseWithElse(x: bool) bool {
    return switch (x) {
        false => true,
        else => false,
    };
}

test "u0" {
    var val: u0 = 0;
    _ = &val;
    switch (val) {
        0 => try expect(val == 0),
    }
}

test "undefined.u0" {
    var val: u0 = undefined;
    _ = &val;
    switch (val) {
        0 => try expect(val == 0),
    }
}

test "switch with disjoint range" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    var q: u8 = 0;
    _ = &q;
    switch (q) {
        0...125 => {},
        127...255 => {},
        126...126 => {},
    }
}

test "switch variable for range and multiple prongs" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    const S = struct {
        fn doTheTest() !void {
            try doTheSwitch(16);
            try doTheSwitch(42);
        }
        fn doTheSwitch(q: u8) !void {
            switch (q) {
                0...40 => |x| try expect(x == 16),
                41, 42, 43 => |x| try expect(x == 42),
                else => try expect(false),
            }
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

var state: u32 = 0;
fn poll() void {
    switch (state) {
        0 => {
            state = 1;
        },
        else => {
            state += 1;
        },
    }
}

test "switch on global mutable var isn't constant-folded" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    while (state < 2) {
        poll();
    }
}

const SwitchProngWithVarEnum = union(enum) {
    One: i32,
    Two: f32,
    Meh: void,
};

test "switch prong with variable" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;

    try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });
    try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });
    try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} });
}
fn switchProngWithVarFn(a: SwitchProngWithVarEnum) !void {
    switch (a) {
        SwitchProngWithVarEnum.One => |x| {
            try expect(x == 13);
        },
        SwitchProngWithVarEnum.Two => |x| {
            try expect(x == 13.0);
        },
        SwitchProngWithVarEnum.Meh => |x| {
            const v: void = x;
            _ = v;
        },
    }
}

test "switch on enum using pointer capture" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try testSwitchEnumPtrCapture();
    try comptime testSwitchEnumPtrCapture();
}

fn testSwitchEnumPtrCapture() !void {
    var value = SwitchProngWithVarEnum{ .One = 1234 };
    switch (value) {
        SwitchProngWithVarEnum.One => |*x| x.* += 1,
        else => unreachable,
    }
    switch (value) {
        SwitchProngWithVarEnum.One => |x| try expect(x == 1235),
        else => unreachable,
    }
}

test "switch handles all cases of number" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testSwitchHandleAllCases();
    try comptime testSwitchHandleAllCases();
}

fn testSwitchHandleAllCases() !void {
    try expect(testSwitchHandleAllCasesExhaustive(0) == 3);
    try expect(testSwitchHandleAllCasesExhaustive(1) == 2);
    try expect(testSwitchHandleAllCasesExhaustive(2) == 1);
    try expect(testSwitchHandleAllCasesExhaustive(3) == 0);

    try expect(testSwitchHandleAllCasesRange(100) == 0);
    try expect(testSwitchHandleAllCasesRange(200) == 1);
    try expect(testSwitchHandleAllCasesRange(201) == 2);
    try expect(testSwitchHandleAllCasesRange(202) == 4);
    try expect(testSwitchHandleAllCasesRange(230) == 3);
}

fn testSwitchHandleAllCasesExhaustive(x: u2) u2 {
    return switch (x) {
        0 => @as(u2, 3),
        1 => 2,
        2 => 1,
        3 => 0,
    };
}

fn testSwitchHandleAllCasesRange(x: u8) u8 {
    return switch (x) {
        0...100 => @as(u8, 0),
        101...200 => 1,
        201, 203 => 2,
        202 => 4,
        204...255 => 3,
    };
}

test "switch on union with some prongs capturing" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const X = union(enum) {
        a,
        b: i32,
    };

    var x: X = X{ .b = 10 };
    _ = &x;
    const y: i32 = switch (x) {
        .a => unreachable,
        .b => |b| b + 1,
    };
    try expect(y == 11);
}

const Number = union(enum) {
    One: u64,
    Two: u8,
    Three: f32,
};

const number = Number{ .Three = 1.23 };

fn returnsFalse() bool {
    switch (number) {
        Number.One => |x| return x > 1234,
        Number.Two => |x| return x == 'a',
        Number.Three => |x| return x > 12.34,
    }
}
test "switch on const enum with var" {
    try expect(!returnsFalse());
}

test "anon enum literal used in switch on union enum" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const Foo = union(enum) {
        a: i32,
    };

    var foo = Foo{ .a = 1234 };
    _ = &foo;
    switch (foo) {
        .a => |x| {
            try expect(x == 1234);
        },
    }
}

test "switch all prongs unreachable" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try testAllProngsUnreachable();
    try comptime testAllProngsUnreachable();
}

fn testAllProngsUnreachable() !void {
    try expect(switchWithUnreachable(1) == 2);
    try expect(switchWithUnreachable(2) == 10);
}

fn switchWithUnreachable(x: i32) i32 {
    while (true) {
        switch (x) {
            1 => return 2,
            2 => break,
            else => continue,
        }
    }
    return 10;
}

test "capture value of switch with all unreachable prongs" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const x = return_a_number() catch |err| switch (err) {
        else => unreachable,
    };
    try expect(x == 1);
}

fn return_a_number() anyerror!i32 {
    return 1;
}

test "switch on integer with else capturing expr" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const S = struct {
        fn doTheTest() !void {
            var x: i32 = 5;
            _ = &x;
            switch (x + 10) {
                14 => return error.TestFailed,
                16 => return error.TestFailed,
                else => |e| try expect(e == 15),
            }
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "else prong of switch on error set excludes other cases" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;

    const S = struct {
        fn doTheTest() !void {
            try expectError(error.C, bar());
        }
        const E = error{
            A,
            B,
        } || E2;

        const E2 = error{
            C,
            D,
        };

        fn foo() E!void {
            return error.C;
        }

        fn bar() E2!void {
            foo() catch |err| switch (err) {
                error.A, error.B => {},
                else => |e| return e,
            };
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "switch prongs with error set cases make a new error set type for capture value" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;

    const S = struct {
        fn doTheTest() !void {
            try expectError(error.B, bar());
        }
        const E = E1 || E2;

        const E1 = error{
            A,
            B,
        };

        const E2 = error{
            C,
            D,
        };

        fn foo() E!void {
            return error.B;
        }

        fn bar() E1!void {
            foo() catch |err| switch (err) {
                error.A, error.B => |e| return e,
                else => {},
            };
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "return result loc and then switch with range implicit casted to error union" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    const S = struct {
        fn doTheTest() !void {
            try expect((func(0xb) catch unreachable) == 0xb);
        }
        fn func(d: u8) anyerror!u8 {
            return switch (d) {
                0xa...0xf => d,
                else => unreachable,
            };
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "switch with null and T peer types and inferred result location type" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const S = struct {
        fn doTheTest(c: u8) !void {
            if (switch (c) {
                0 => true,
                else => null,
            }) |v| {
                _ = v;
                return error.TestFailed;
            }
        }
    };
    try S.doTheTest(1);
    try comptime S.doTheTest(1);
}

test "switch prongs with cases with identical payload types" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const Union = union(enum) {
        A: usize,
        B: isize,
        C: usize,
    };
    const S = struct {
        fn doTheTest() !void {
            try doTheSwitch1(Union{ .A = 8 });
            try doTheSwitch2(Union{ .B = -8 });
        }
        fn doTheSwitch1(u: Union) !void {
            switch (u) {
                .A, .C => |e| {
                    comptime assert(@TypeOf(e) == usize);
                    try expect(e == 8);
                },
                .B => |e| {
                    _ = e;
                    return error.TestFailed;
                },
            }
        }
        fn doTheSwitch2(u: Union) !void {
            switch (u) {
                .A, .C => |e| {
                    _ = e;
                    return error.TestFailed;
                },
                .B => |e| {
                    comptime assert(@TypeOf(e) == isize);
                    try expect(e == -8);
                },
            }
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "switch prong pointer capture alignment" {
    const U = union(enum) {
        a: u8 align(8),
        b: u8 align(4),
        c: u8,
    };

    const S = struct {
        fn doTheTest() !void {
            const u = U{ .a = 1 };
            switch (u) {
                .a => |*a| comptime assert(@TypeOf(a) == *align(8) const u8),
                .b, .c => |*p| {
                    _ = p;
                    return error.TestFailed;
                },
            }

            switch (u) {
                .a, .b => |*p| comptime assert(@TypeOf(p) == *align(4) const u8),
                .c => |*p| {
                    _ = p;
                    return error.TestFailed;
                },
            }

            switch (u) {
                .a, .c => |*p| comptime assert(@TypeOf(p) == *const u8),
                .b => |*p| {
                    _ = p;
                    return error.TestFailed;
                },
            }
        }

        fn doTheTest2() !void {
            const un1 = U{ .b = 1 };
            switch (un1) {
                .b => |*b| comptime assert(@TypeOf(b) == *align(4) const u8),
                .a, .c => |*p| {
                    _ = p;
                    return error.TestFailed;
                },
            }

            const un2 = U{ .c = 1 };
            switch (un2) {
                .c => |*c| comptime assert(@TypeOf(c) == *const u8),
                .a, .b => |*p| {
                    _ = p;
                    return error.TestFailed;
                },
            }
        }
    };

    try S.doTheTest();
    try comptime S.doTheTest();

    try S.doTheTest2();
    try comptime S.doTheTest2();
}

test "switch on pointer type" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        const X = struct {
            field: u32,
        };

        const P1 = @as(*X, @ptrFromInt(0x400));
        const P2 = @as(*X, @ptrFromInt(0x800));
        const P3 = @as(*X, @ptrFromInt(0xC00));

        fn doTheTest(arg: *X) i32 {
            switch (arg) {
                P1 => return 1,
                P2 => return 2,
                else => return 3,
            }
        }
    };

    try expect(1 == S.doTheTest(S.P1));
    try expect(2 == S.doTheTest(S.P2));
    try expect(3 == S.doTheTest(S.P3));
    comptime assert(1 == S.doTheTest(S.P1));
    comptime assert(2 == S.doTheTest(S.P2));
    comptime assert(3 == S.doTheTest(S.P3));
}

test "switch on error set with single else" {
    const S = struct {
        fn doTheTest() !void {
            var some: error{Foo} = error.Foo;
            _ = &some;
            try expect(switch (some) {
                else => blk: {
                    break :blk true;
                },
            });
        }
    };

    try S.doTheTest();
    try comptime S.doTheTest();
}

test "switch capture copies its payload" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest() !void {
            var tmp: union(enum) {
                A: u8,
                B: u32,
            } = .{ .A = 42 };
            switch (tmp) {
                .A => |value| {
                    // Modify the original union
                    tmp = .{ .B = 0x10101010 };
                    try expectEqual(@as(u8, 42), value);
                },
                else => unreachable,
            }
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "capture of integer forwards the switch condition directly" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    const S = struct {
        fn foo(x: u8) !void {
            switch (x) {
                40...45 => |capture| {
                    try expect(capture == 42);
                },
                else => |capture| {
                    try expect(capture == 100);
                },
            }
        }
    };
    try S.foo(42);
    try S.foo(100);
    try comptime S.foo(42);
    try comptime S.foo(100);
}

test "enum value without tag name used as switch item" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const E = enum(u32) {
        a = 1,
        b = 2,
        _,
    };
    var e: E = @enumFromInt(0);
    _ = &e;
    switch (e) {
        @as(E, @enumFromInt(0)) => {},
        .a => return error.TestFailed,
        .b => return error.TestFailed,
        _ => return error.TestFailed,
    }
}

test "switch item sizeof" {
    const S = struct {
        fn doTheTest() !void {
            var a: usize = 0;
            _ = &a;
            switch (a) {
                @sizeOf(struct {}) => {},
                else => return error.TestFailed,
            }
        }
    };
    try S.doTheTest();
    try comptime S.doTheTest();
}

test "comptime inline switch" {
    const U = union(enum) { a: type, b: type };
    const value = comptime blk: {
        var u: U = .{ .a = u32 };
        _ = &u;
        break :blk switch (u) {
            inline .a, .b => |v| v,
        };
    };

    try expectEqual(u32, value);
}

test "switch capture peer type resolution" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const U = union(enum) {
        a: u32,
        b: u64,
        fn innerVal(u: @This()) u64 {
            switch (u) {
                .a, .b => |x| return x,
            }
        }
    };

    try expectEqual(@as(u64, 100), U.innerVal(.{ .a = 100 }));
    try expectEqual(@as(u64, 200), U.innerVal(.{ .b = 200 }));
}

test "switch capture peer type resolution for in-memory coercible payloads" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;

    const T1 = c_int;
    const T2 = @Type(@typeInfo(T1));

    comptime assert(T1 != T2);

    const U = union(enum) {
        a: T1,
        b: T2,
        fn innerVal(u: @This()) c_int {
            switch (u) {
                .a, .b => |x| return x,
            }
        }
    };

    try expectEqual(@as(c_int, 100), U.innerVal(.{ .a = 100 }));
    try expectEqual(@as(c_int, 200), U.innerVal(.{ .b = 200 }));
}

test "switch pointer capture peer type resolution" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;

    const T1 = c_int;
    const T2 = @Type(@typeInfo(T1));

    comptime assert(T1 != T2);

    const U = union(enum) {
        a: T1,
        b: T2,
        fn innerVal(u: *@This()) *c_int {
            switch (u.*) {
                .a, .b => |*ptr| return ptr,
            }
        }
    };

    var ua: U = .{ .a = 100 };
    var ub: U = .{ .b = 200 };

    ua.innerVal().* = 111;
    ub.innerVal().* = 222;

    try expectEqual(U{ .a = 111 }, ua);
    try expectEqual(U{ .b = 222 }, ub);
}

test "inline switch range that includes the maximum value of the switched type" {
    const inputs: [3]u8 = .{ 0, 254, 255 };
    for (inputs) |input| {
        switch (input) {
            inline 254...255 => |val| try expectEqual(input, val),
            else => |val| try expectEqual(input, val),
        }
    }
}

test "nested break ignores switch conditions and breaks instead" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn register_to_address(ident: []const u8) !u8 {
            const reg: u8 = if (std.mem.eql(u8, ident, "zero")) 0x00 else blk: {
                break :blk switch (ident[0]) {
                    0x61 => (try std.fmt.parseInt(u8, ident[1..], 0)) + 1,
                    0x66 => (try std.fmt.parseInt(u8, ident[1..], 0)) + 1,
                    else => {
                        break :blk 0xFF;
                    },
                };
            };
            return reg;
        }
    };
    // Originally reported at https://github.com/ziglang/zig/issues/10196
    try expect(0x01 == try S.register_to_address("a0"));
}

test "peer type resolution on switch captures ignores unused payload bits" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;

    const Foo = union(enum) {
        a: u32,
        b: u64,
    };

    var val: Foo = undefined;
    @memset(std.mem.asBytes(&val), 0xFF);

    // This is runtime-known so the following store isn't comptime-known.
    var rt: u32 = 123;
    _ = &rt;
    val = .{ .a = rt }; // will not necessarily zero remaning payload memory

    // Fields intentionally backwards here
    const x = switch (val) {
        .b, .a => |x| x,
    };

    try expect(x == 123);
}

test "switch prong captures range" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    const S = struct {
        fn a(b: []u3, c: u3) void {
            switch (c) {
                0...1 => b[c] = c,
                2...3 => b[c] = c,
                4...7 => |d| b[d] = c,
            }
        }
    };

    var arr: [8]u3 = undefined;
    S.a(&arr, 5);
    try expect(arr[5] == 5);
}

test "prong with inline call to unreachable" {
    const U = union(enum) {
        void: void,
        bool: bool,

        inline fn unreach() noreturn {
            unreachable;
        }
    };
    var u: U = undefined;
    u = .{ .bool = true };
    switch (u) {
        .void => U.unreach(),
        .bool => |ok| try expect(ok),
    }
}

test "block error return trace index is reset between prongs" {
    const S = struct {
        fn returnError() error{TestFailed} {
            return error.TestFailed;
        }
    };

    var x: u1 = 0;
    _ = &x;

    const result = switch (x) {
        0 => {
            const result: anyerror!i32 = blk: {
                break :blk 1;
            };
            _ = &result;
        },
        1 => blk: {
            const err = switch (x) {
                0 => {},
                1 => S.returnError(),
            };
            break :blk err;
        },
    };
    try result;
}

test "labeled switch with break" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    var six: u32 = undefined;
    six = 6;

    const val = s: switch (six) {
        0...4 => break :s false,
        5 => break :s false,
        6...7 => break :s true,
        else => break :s false,
    };

    try expect(val);

    // Make sure the switch is implicitly comptime!
    const comptime_val = s: switch (@as(u32, 6)) {
        0...4 => break :s false,
        5 => break :s false,
        6...7 => break :s true,
        else => break :s false,
    };

    comptime assert(comptime_val);
}

test "unlabeled break ignores switch" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    const result = while (true) {
        _ = s: switch (@as(u32, 1)) {
            1 => continue :s 123,
            else => |x| break x,
        };
        comptime unreachable; // control flow never breaks from the switch
    };
    try expect(result == 123);
}

test "switch on a signed value smaller than the smallest prong value" {
    var v: i32 = undefined;
    v = -1;
    switch (v) {
        inline 0...10 => return error.TestFailed,
        else => {},
    }
}

test "switch on 8-bit mod result" {
    var x: u8 = undefined;
    x = 16;
    switch (x % 4) {
        0 => {},
        1, 2, 3 => return error.TestFailed,
        else => unreachable,
    }
}

test "switch on non-exhaustive enum" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO

    const E = enum(u4) {
        a,
        b,
        c,
        _,

        fn doTheTest(e: @This()) !void {
            switch (e) {
                .a, .b => {},
                else => return error.TestFailed,
            }
            switch (e) {
                .a, .b => {},
                .c => return error.TestFailed,
                _ => return error.TestFailed,
            }
            switch (e) {
                .a, .b => {},
                .c, _ => return error.TestFailed,
            }
            switch (e) {
                .a => {},
                .b, .c, _ => return error.TestFailed,
            }
            switch (e) {
                .b => return error.TestFailed,
                else => {},
                _ => return error.TestFailed,
            }
            switch (e) {
                else => {},
                _ => return error.TestFailed,
            }
            switch (e) {
                inline else => {},
                _ => return error.TestFailed,
            }
        }
    };

    try E.doTheTest(.a);
    try comptime E.doTheTest(.a);
}
